package edu.pku.sei.mte.serialization;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import edu.pku.sei.mte.mtemodel.basictypes.PrimitiveType;
import edu.pku.sei.mte.mtemodel.core.DataType;
import edu.pku.sei.mte.mtemodel.metamodel.RTypeGraph;
import edu.pku.sei.mte.mtemodel.mtmodel.ModelType;
import edu.pku.sei.mte.mtemodel.mtmodel.common.Key;
import edu.pku.sei.mte.mtemodel.mtmodel.expression.AddExpression;
import edu.pku.sei.mte.mtemodel.mtmodel.expression.BindExpression;
import edu.pku.sei.mte.mtemodel.mtmodel.expression.CallRelationExpression;
import edu.pku.sei.mte.mtemodel.mtmodel.expression.CallRuleExpression;
import edu.pku.sei.mte.mtemodel.mtmodel.expression.ConstantExpression;
import edu.pku.sei.mte.mtemodel.mtmodel.expression.EqualExpression;
import edu.pku.sei.mte.mtemodel.mtmodel.expression.Expression;
import edu.pku.sei.mte.mtemodel.mtmodel.expression.NACExpression;
import edu.pku.sei.mte.mtemodel.mtmodel.expression.PACExpression;
import edu.pku.sei.mte.mtemodel.mtmodel.pattern.TPatternEdge;
import edu.pku.sei.mte.mtemodel.mtmodel.pattern.TPatternGraph;
import edu.pku.sei.mte.mtemodel.mtmodel.pattern.TPatternNode;
import edu.pku.sei.mte.mtemodel.mtmodel.rulebased.Domain;
import edu.pku.sei.mte.mtemodel.mtmodel.rulebased.RBTransformation;
import edu.pku.sei.mte.mtemodel.mtmodel.rulebased.Relation;

/**
 * @author zhubin
 *
 */
public class EcoreTransformationLoader {
	DocumentBuilderFactory factory = null;
	RBTransformation trans = null;
	
	public EcoreTransformationLoader(){
		factory = DocumentBuilderFactory.newInstance();
		factory.setIgnoringComments(true);
		factory.setIgnoringElementContentWhitespace(true);
		factory.setNamespaceAware(true);
	}
	
	public RBTransformation load(String pathname,Map<String,RTypeGraph> typeMap){
		File file = new File(pathname);
		if(file.exists()==false){
			System.out.println(pathname+" dose not exist");
			return null;
		}
		
		try {
			trans = load(file);
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		return trans;
		
	}

	public RBTransformation load(File file) throws ParserConfigurationException, SAXException, IOException {
		if(file.exists()==false){
			System.out.println("file not exist!");
			return null;
		}
		DocumentBuilder builder = factory.newDocumentBuilder();
		Document doc = builder.parse(file);
		
		Element root = doc.getDocumentElement();
		return load(root);
	}
	
	public RBTransformation load(Element root){
		trans = new RBTransformation();
		trans.setName(root.getAttribute("name"));
		
		loadTypeKeys(root.getElementsByTagName("typekey"));
		loadModelTypes(root.getElementsByTagName("modeltype"));
		
		NodeList relationNodes = root.getElementsByTagName("rules").item(0).getChildNodes();
		int length = relationNodes.getLength();
		for(int i=0;i<length;i++){
			
			Node item = relationNodes.item(i);
			if(item.getNodeType() != Node.ELEMENT_NODE)
				continue;
			Element relationNode = (Element) item;
			loadRelation(relationNode);
		}
		return trans;
	}
	
	private void loadTypeKeys(NodeList list){
		int length = list.getLength();
		for(int i=0;i<length;i++){
			Node item = list.item(i);
			if(item.getNodeType() != Node.ELEMENT_NODE)
				continue;
			Element node = (Element) item;
			String typeName = node.getAttribute("name");
			String typeFileName = node.getAttribute("file");
			EcoreLoader loader = EcoreLoader.getEcoreLoader(typeName, typeFileName);
			RTypeGraph types = loader.load();
			NodeList subNodes = node.getChildNodes();
			for(int j=0;j<subNodes.getLength();j++){
				Node subItem = subNodes.item(j);
				if(subItem.getNodeType() != Node.ELEMENT_NODE)
					continue;
				Element subNode = (Element) subItem;
				if(!subNode.getNodeName().equals("keys"))
					continue;
				String subTypeName = subNode.getAttribute("type");
				NodeList subsubNodes = subNode.getChildNodes();
				List<String> keyList = new ArrayList<String>();
				for(int k=0;k<subsubNodes.getLength();k++){
					Node subsubItem = subsubNodes.item(k);
					if(subsubItem.getNodeType() != Node.ELEMENT_NODE)
						continue;
					Element subsubNode = (Element) subsubItem;
					if(!subsubNode.getNodeName().equals("key"))
						continue;
					String keyString = subsubNode.getTextContent();
					keyList.add(keyString);
				}
				Key key = new Key();
				key.setFactors(keyList.toArray(new String[0]));
				trans.addObjectKey(types.getTypeClass(subTypeName), key);
			}
		}
	}
	
	private void loadModelTypes(NodeList list){
		int length = list.getLength();
		for(int i=0;i<length;i++){
			ModelType mt = new ModelType();
			Node item = list.item(i);
			if(item.getNodeType() != Node.ELEMENT_NODE)
				continue;
			Element node = (Element) item;
			mt.setName(node.getAttribute("name"));
			mt.setTypeGraph(EcoreLoader.getRType(node.getAttribute("type")));
			trans.addModelType(mt);
		}
	}
	
	private void loadRelation(Element elem){
		if(!elem.getNodeName().equals("relation"))
			return;
		Relation relation = new Relation();
		relation.setName(elem.getAttribute("name"));
		relation.setPriority(Integer.parseInt(elem.getAttribute("priority")));
		NodeList childs = elem.getChildNodes();
		int length = childs.getLength();
		for(int i=0;i<length;i++){
			Node item = childs.item(i);
			if(item.getNodeType() != Node.ELEMENT_NODE)
				continue;
			Element child = (Element) item;
			if(child.getNodeName().equals("variables")){
				NodeList variableNodes = child.getChildNodes();
				int numOfVar = variableNodes.getLength();
				for(int j=0;j<numOfVar;j++){
					Node item2 = variableNodes.item(j);
					if(item2.getNodeType() != Node.ELEMENT_NODE)
						continue;
					loadVariable((Element) item2,relation);
				}
			}else if(child.getNodeName().equals("domain")){
				relation.addDomain(loadDomain(child));
			}else if(child.getNodeName().equals("when")){
				relation.getWhenClause().addAll(loadWhenOrWhereClause(child));
			}else if(child.getNodeName().equals("where")){
				relation.getWhereClause().addAll(loadWhenOrWhereClause(child));
			}
		}
		trans.addRule(relation);
	}
	
	private void loadVariable(Element elem, Relation relation){
		if(!elem.getNodeName().equals("variable"))
			return;
		DataType dataType = null;
		if(elem.getAttribute("type").equals("String")){
			dataType = PrimitiveType.TYPE_STRING;
		}else if(elem.getAttribute("type").equals("Integer")){
			dataType = PrimitiveType.TYPE_INTEGER;
		}else if(elem.getAttribute("type").equals("Boolean")){
			dataType = PrimitiveType.TYPE_BOOLEAN;
		}
		relation.declareVariable(elem.getAttribute("name"), dataType);
	}
	
	private Domain loadDomain(Element elem){
		Domain d = new Domain();
		d.setModelType(trans.getModelType(elem.getAttribute("modeltype")));
		NodeList nodes = elem.getChildNodes();
		for(int i = 0;i<nodes.getLength();i++){
			Node item = nodes.item(i);
			if(item.getNodeType() != Node.ELEMENT_NODE)
				continue;
			d.setPattern(loadPattern((Element) item, d));
		}
		
		return d;
	}
	
	private TPatternGraph loadPattern(Element elem, Domain domain){
		TPatternGraph tpg = new TPatternGraph();
		NodeList nodes = elem.getChildNodes();
		NodeList subNodes = null;
		List<TPatternNode> pNodes = new ArrayList<TPatternNode>();
		for(int i=0;i<nodes.getLength();i++){
			Node item = nodes.item(i);
			if(item.getNodeType() != Node.ELEMENT_NODE)
				continue;
			Element e = (Element) item;
			subNodes = e.getChildNodes();
			if(e.getNodeName().equals("nodes")){
				for(int j=0;j<subNodes.getLength();j++){
					Node item2 = subNodes.item(j);
					if(item2.getNodeType() != Node.ELEMENT_NODE)
						continue;
					TPatternNode tpn = loadPatternNode((Element) item2, domain);
					tpg.addNode(tpn);
					pNodes.add(tpn);
				}
			}else if(e.getNodeName().equals("edges")){
				for(int j=0;j<subNodes.getLength();j++){
					Node item2 = subNodes.item(j);
					if(item2.getNodeType() != Node.ELEMENT_NODE)
						continue;
					tpg.addEdge(loadPatternEdge((Element) item2, domain, pNodes));
				}
			}else if(e.getNodeName().equals("expressions")){
				for(int j=0;j<subNodes.getLength();j++){
					Node item2 = subNodes.item(j);
					if(item2.getNodeType() != Node.ELEMENT_NODE)
						continue;
					tpg.getTemplateExpressions().add(loadExpression((Element) item2));
				}
			}
		}
		return tpg;
	}
	
	private TPatternNode getPatternNode(List<TPatternNode> patternNodes, String name){
		Iterator<TPatternNode> iter = patternNodes.iterator();
		while(iter.hasNext()){
			TPatternNode node = iter.next();
			if(node.getName().equals(name))
				return node;
		}
		return null;
	}
	
	private TPatternNode loadPatternNode(Element elem, Domain domain){
		TPatternNode pNode = new TPatternNode();
		pNode.setName(elem.getAttribute("name"));
		pNode.setType(domain.getModelType().getTypeGraph().getTypeClass(elem.getAttribute("type")));
		return pNode;
	}
	
	private TPatternEdge loadPatternEdge(Element elem, Domain domain, List<TPatternNode> patternNodes){
		TPatternEdge pEdge = new TPatternEdge();
		String srcName = elem.getAttribute("srcName");
		String tarName = elem.getAttribute("tarName");
		pEdge.setSource(getPatternNode(patternNodes, elem.getAttribute("source")));
		pEdge.setTarget(getPatternNode(patternNodes, elem.getAttribute("target")));
		pEdge.setType(domain.getModelType().getTypeGraph().getTypeReference(null, pEdge.getSource().getType(), srcName, pEdge.getTarget().getType(), tarName));
		return pEdge;
	}
	
	private List<Expression> loadWhenOrWhereClause(Element elem){
		List<Expression> expressions = new ArrayList<Expression>();
		NodeList nodes = elem.getChildNodes();
		int length = nodes.getLength();
		for(int i=0;i<length;i++){
			Node item = nodes.item(i);
			if(item.getNodeType() != Node.ELEMENT_NODE)
				continue;
			Element e = (Element) item;
			expressions.add(loadExpression(e));
		}
		return expressions;
	}
	
	public static Expression loadExpression(Element elem){
		if(elem.getAttribute("name").equals("CallRelationExpression")){
			return loadCallRelationExpression(elem);
		}else if(elem.getAttribute("name").equals("BindExpression")){
			return loadBindExpression(elem);
		}else if(elem.getAttribute("name").equals("EqualExpression")){
			return loadEqualExpression(elem);
		}else if(elem.getAttribute("name").equals("NACExpression")){
			return loadNACExpression(elem);
		}else if(elem.getAttribute("name").equals("PACExpression")){
			return loadPACExpression(elem);
		}else if(elem.getAttribute("name").equals("ConstantExpression")){
			return loadConstantExpression(elem);
		}else if(elem.getAttribute("name").equals("AddExpression")){
			return loadAddExpression(elem);
		}
		return null;
	}
	
	private static Expression loadCallRelationExpression(Element elem){
		NodeList nodes = elem.getChildNodes();
		int length = nodes.getLength();
		CallRelationExpression expr = new CallRelationExpression();
		for(int i=0;i<length;i++){
			Node item = nodes.item(i);
			if(item.getNodeType() != Node.ELEMENT_NODE)
				continue;
			Element e = (Element) item;
			if(e.getNodeName().equals("relation")){
				expr.setRuleName(e.getTextContent());
			}else if(e.getNodeName().equals("parameter")){
				loadParameter(e, expr);
			}
		}
		return expr;
	}
	
	private static void loadParameter(Element elem, CallRuleExpression callRuleExpression){
		Expression from = null;
		Expression to = null;
		NodeList nodes = elem.getChildNodes();
		for(int i=0;i<nodes.getLength();i++){
			Node item = nodes.item(i);
			if(item.getNodeType() != Node.ELEMENT_NODE)
				continue;
			Element e = (Element) item;
			if(e.getNodeName().equals("from")){
				NodeList childNodes = e.getChildNodes();
				for(int j=0;j<childNodes.getLength();j++){
					Node item2 = childNodes.item(j);
					if(item2.getNodeType() != Node.ELEMENT_NODE)
						continue;
					Element e2 = (Element) item2;
					from = loadExpression(e2);
					break;
				}
			}else if(e.getNodeName().equals("to")){
				NodeList childNodes = e.getChildNodes();
				for(int j=0;j<childNodes.getLength();j++){
					Node item2 = childNodes.item(j);
					if(item2.getNodeType() != Node.ELEMENT_NODE)
						continue;
					Element e2 = (Element) item2;
					to = loadExpression(e2);
					break;
				}
			}
		}
		if(from != null && to != null){
			callRuleExpression.setParameter(from, to);
		}
	}
	
	private static Expression loadBindExpression(Element elem){
		BindExpression expr = new BindExpression();
		NodeList nodes = elem.getChildNodes();
		for(int i=0;i<nodes.getLength();i++){
			Node item = nodes.item(i);
			if(item.getNodeType() != Node.ELEMENT_NODE)
				continue;
			Element e = (Element) item;
			expr.setName(e.getTextContent());
			break;
		}
		return expr;
	}
	
	private static Expression loadEqualExpression(Element elem){
		Expression left = null;
		Expression right = null;
		NodeList nodes = elem.getChildNodes();
		for(int i=0;i<nodes.getLength();i++){
			Node item = nodes.item(i);
			if(item.getNodeType() != Node.ELEMENT_NODE)
				continue;
			Element e = (Element) item;
			if(e.getNodeName().equals("left")){
				NodeList childNodes = e.getChildNodes();
				for(int j=0;j<childNodes.getLength();j++){
					Node item2 = childNodes.item(j);
					if(item2.getNodeType() != Node.ELEMENT_NODE)
						continue;
					Element e2 = (Element) item2;
					left = loadExpression(e2);
					break;
				}
			}else if(e.getNodeName().equals("right")){
				NodeList childNodes = e.getChildNodes();
				for(int j=0;j<childNodes.getLength();j++){
					Node item2 = childNodes.item(j);
					if(item2.getNodeType() != Node.ELEMENT_NODE)
						continue;
					Element e2 = (Element) item2;
					right = loadExpression(e2);
					break;
				}
			}
		}
		if(left != null && right != null){
			EqualExpression expr = new EqualExpression();
			expr.setLeft(left);
			expr.setRight(right);
			return expr;
		}
		return null;
	}

	private static Expression loadNACExpression(Element elem){
		NodeList nodes = elem.getChildNodes();
		int length = nodes.getLength();
		NACExpression expr = new NACExpression();
		for(int i=0;i<length;i++){
			Node item = nodes.item(i);
			if(item.getNodeType() != Node.ELEMENT_NODE)
				continue;
			Element e = (Element) item;
			if(e.getNodeName().equals("relation")){
				expr.setRuleName(e.getTextContent());
			}else if(e.getNodeName().equals("parameter")){
				loadParameter(e, expr);
			}
		}
		return expr;
	}
	
	private static Expression loadPACExpression(Element elem){
		NodeList nodes = elem.getChildNodes();
		int length = nodes.getLength();
		PACExpression expr = new PACExpression();
		for(int i=0;i<length;i++){
			Node item = nodes.item(i);
			if(item.getNodeType() != Node.ELEMENT_NODE)
				continue;
			Element e = (Element) item;
			if(e.getNodeName().equals("relation")){
				expr.setRuleName(e.getTextContent());
			}else if(e.getNodeName().equals("parameter")){
				loadParameter(e, expr);
			}
		}
		return expr;
	}
	
	private static Expression loadConstantExpression(Element elem){
		ConstantExpression expr = new ConstantExpression();
		expr.setValue(elem.getTextContent().trim());
		return expr;
	}
	
	private static Expression loadAddExpression(Element elem){
		Expression left = null;
		Expression right = null;
		NodeList nodes = elem.getChildNodes();
		for(int i=0;i<nodes.getLength();i++){
			Node item = nodes.item(i);
			if(item.getNodeType() != Node.ELEMENT_NODE)
				continue;
			Element e = (Element) item;
			if(e.getNodeName().equals("left")){
				NodeList childNodes = e.getChildNodes();
				for(int j=0;j<childNodes.getLength();j++){
					Node item2 = childNodes.item(j);
					if(item2.getNodeType() != Node.ELEMENT_NODE)
						continue;
					Element e2 = (Element) item2;
					left = loadExpression(e2);
					break;
				}
			}else if(e.getNodeName().equals("right")){
				NodeList childNodes = e.getChildNodes();
				for(int j=0;j<childNodes.getLength();j++){
					Node item2 = childNodes.item(j);
					if(item2.getNodeType() != Node.ELEMENT_NODE)
						continue;
					Element e2 = (Element) item2;
					right = loadExpression(e2);
					break;
				}
			}
		}
		if(left != null && right != null){
			AddExpression expr = new AddExpression();
			expr.setLeft(left);
			expr.setRight(right);
			return expr;
		}
		return null;
	}
}
